/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import {DownloadTask} from './DownloadTask'
import {StartEndListener} from './listener/StartEndListener'
import {EndCause} from './cause/EndCause'
import {DownloadListener} from './DownloadListener'
import {DownloadListenerBunch} from './listener/DownloadListenerBunch'
import {Log} from './Util'
/**
 * The serial queue, what used to dynamically increase tasks, and tasks in the queue will
 * automatically start download one by one.
 */

const TAG: string = "DownloadSerialQueue";

export class DownloadSerialQueue extends StartEndListener {

    private shutedDown: boolean = false;
    private looping: boolean = false;
    private paused: boolean = false;

    private runningTask: DownloadTask;
    private taskList: Array<DownloadTask>;

    static readonly ID_INVALID: number = 0;

    listenerBunch: DownloadListenerBunch;

    public constructor(listener?: DownloadListener, taskList: Array<DownloadTask> = new Array) {
        super();
        this.listenerBunch = new DownloadListenerBunch.Builder()
                .append(this)
                .append(listener).build();

        this.taskList = taskList;
    }

    public setListener(listener: DownloadListener): void {
        this.listenerBunch = new DownloadListenerBunch.Builder()
                .append(this)
                .append(listener).build();
    }

    /**
     * Enqueues the given task sometime in the serial queue. If the {@code task} is in the head of
     * the serial queue, the {@code task} will be started automatically.
     */
    public enqueue(task: DownloadTask): void {
        this.taskList.push(task);
        this.taskList.sort()
        //Collections.sort(taskList);

        if (!this.paused && !this.looping) {
            this.looping = true;
            this.startNewLooper();
        }
    }

    /**
     * Pause the queue.
     *
     * @see #resume()
     */
    public pause(): void {
        if (this.paused) {
            Log.showInfo(TAG, "require pause this queue(remain " + this.taskList.length + "), but"
                    + "it has already been paused");
            return;
        }
        this.paused = true;

        if (this.runningTask != null) {
            this.runningTask.cancel();
            this.taskList.unshift(this.runningTask);
            this.runningTask = null;
        }
    }

    /**
     * Resume the queue if the queue is paused.
     *
     * @see #pause()
     */
    public resume(): void {
        if (!this.paused) {
            Log.showInfo(TAG, "require resume this queue(remain " + this.taskList.length + "), but it is"
                    + " still running");
            return;
        }
        this.paused = false;

        if (this.taskList.length != 0 && !this.looping) {
            this.looping = true;
            this.startNewLooper();
        }
    }

    /**
     * Returns the identify of the working task, if there is task is working, you will receive
     * {@link #ID_INVALID}.
     *
     * @return the identify of the working task
     */
    public getWorkingTaskId(): number {
        return this.runningTask != null ? this.runningTask.getId() : DownloadSerialQueue.ID_INVALID;
    }

    /**
     * Get the count of tasks which is waiting on this queue.
     *
     * @return the count of waiting tasks on this queue.
     */
    public getWaitingTaskCount(): number {
        return this.taskList.length;
    }

    /**
     * Attempts to stop the working task, halts the processing of waiting tasks, and returns a list
     * of the tasks that were awaiting execution. These tasks are drained (removed) from the task
     * queue upon return from this method.
     */
    public shutdown(): DownloadTask[] {
        this.shutedDown = true;

        if (this.runningTask != null) this.runningTask.cancel();

        const tasks: DownloadTask[] = {...this.taskList}
        this.taskList.length =0;

        return tasks;
    }

    public run(): void {
        while (!this.shutedDown) {
            let nextTask: DownloadTask;
                if (this.taskList.length == 0 || this.paused) {
                    this.runningTask = null;
                    this.looping = false;
                    break;
                }

                nextTask = this.taskList.splice(0, 1)[0];

            nextTask.execute(this.listenerBunch);
        }
    }

    startNewLooper(): void {
        this.run();
    }

    public taskStart(task: DownloadTask): void {
        this.runningTask = task;
    }

    public taskEnd(task: DownloadTask, cause: EndCause, realCause: Error): void{
        if (cause != EndCause.CANCELED && task == this.runningTask) this.runningTask = null;
    }
}
